package com.zzyl.nursing.service.impl;

import java.time.LocalTime;
import java.util.*;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.nursing.config.WebSocketServer;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertDataMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import com.zzyl.system.mapper.SysUserMapper;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.zzyl.common.constant.Constants;

/**
 * 报警规则Service业务层处理
 * 
 * @author ruoyi
 * @date 2025-08-04
 */
@Slf4j
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private WebSocketServer webSocketServer;
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return removeById(id) ? 1 : 0;
    }

    @Override
    public void alertFilter() {
//        1.查询所有规则
        long count = this.count(Wrappers.<AlertRule>lambdaQuery().eq(
                AlertRule::getStatus, 1
        ));
        if (count<=0) {
//            如果没有规则,直接结束
            return;
        }
//        2.查询所有规则上报的数据
        List<Object> deviceDataList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (CollUtil.isEmpty(deviceDataList)) {
//            查询数据为空直接返回
            return;
        }
        List<DeviceData> deviceDatas = new ArrayList<>();
//        3.解析上报数据
        deviceDataList.forEach(deviceData -> {
//            先将数据转换为具体对象
            List<DeviceData> list = JSONUtil.toList(deviceData.toString(), DeviceData.class);
            deviceDatas.addAll(list);

        });
//        4.数据报警处理
        deviceDatas.forEach(deviceData -> {
            alertFilter(deviceData);
        });

    }
    @Override
    public void alertFilter(DeviceData deviceData) {
//        查询全部规则
        List<AlertRule> alertRuleList = this.list(
                Wrappers.<AlertRule>lambdaQuery()
                        .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                        .eq(AlertRule::getStatus, 1)
                        .eq(AlertRule::getProductKey,deviceData.getProductKey())
        );
        if (alertRuleList.isEmpty()) {
//            如果规则为空直接返回
            return;
        }
        alertRuleList.forEach(alertRule -> {
            deviceDataAlarmHandle(alertRule,deviceData);
        });


    }

    private void deviceDataAlarmHandle(AlertRule alertRule, DeviceData deviceData) {
        //        第一层校验:判断生效时间
        String time = alertRule.getAlertEffectivePeriod();
        String[] split = time.split("~");
        LocalTime startTime = LocalTime.parse(split[0]);
        LocalTime endTime = LocalTime.parse(split[1]);
//        获取数据上报时间
        LocalTime alarmTime = LocalDateTimeUtil.of(deviceData.getAlarmTime()).toLocalTime();
        if (alarmTime.isBefore(startTime)||alarmTime.isAfter(endTime)) {
//            由于时间不在范围内,直接返回
            return;
        }
        //        第二层校验:判断数据阈值
//        取出阈值
        String iotId = deviceData.getIotId();
        Long value = alertRule.getValue();
        String key = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + iotId+":"+deviceData.getFunctionId()+":"+alertRule.getId();
        Double dataValue = Double.valueOf(deviceData.getDataValue());
//        对比比较符,使用NumberUtil.compare进行比较是否超过阈值,0为相等,如果值1大于值2则返回正值,小于则返回负值
        int compare = NumberUtil.compare(dataValue, value);
        if (alertRule.getOperator().equals(">=")&&compare>=0||alertRule.getOperator().equals("<")&&compare<0) {
            log.info("设备值超过阈值");
        }
        else {
            log.info("设备值未超过阈值");
            redisTemplate.delete( key);
            return;
        }
        //        第三层校验:判断沉默周期
        //        第四层校验:判断持续周期

        // 沉默周期  持续周期
        // 设计一个redis的可以，必须唯一，代表的当前的设备、物模型、规则ID
        String silentKey = CacheConstants.IOT_SILENT_ALERT+deviceData.getIotId()+":"+deviceData.getFunctionId()+":"+alertRule.getId();
        // 获取沉默周期
        String silentData = (String) redisTemplate.opsForValue().get(silentKey);
        if(StringUtils.isNotEmpty(silentData)) {
            return;
        }
        // 持续周期

        String aggCountData = (String) redisTemplate.opsForValue().get(key);
        Integer count = StringUtils.isEmpty(aggCountData)? 1 : (Integer.parseInt(aggCountData) + 1);
        // 当前count不等于持续周期，就累加数据，并且结束请求
        if(ObjectUtil.notEqual(count,alertRule.getDuration())) {
            // 累加数据
            redisTemplate.opsForValue().set(key,count+"");
            return;
        }
        // 到了报警的条件了，保存一份沉默周期
        redisTemplate.opsForValue().set(silentKey,"1", alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
        // 删除统计的次数
        redisTemplate.delete(key);

        // 保存异常数据
        // 判断上报数据的设备的类型，如果老人的异常数据（手表、睡眠检测带） 设备异常（烟雾报警）
        List<Long> userIds = new ArrayList<>();
        if(alertRule.getAlertDataType().equals(0)) {
            // 老人异常（手表、睡眠检测带）  设备id-->设备-->老人id--->护理员
            if(deviceData.getLocationType().equals(0)) {
                // 随身设备
                userIds = deviceMapper.selectNursingIdsByIotIdWithElder(deviceData.getIotId());
            }else if(deviceData.getLocationType().equals(1) && deviceData.getPhysicalLocationType().equals(2)){
                // 床位设备  设备id-->设备-->床位-->老人id--->护理员
                userIds = deviceMapper.selectNursingIdsByIotIdWithBed(deviceData.getIotId());
            }
        }else {
            // 设备异常  找维修人员   通过角色名称（维修工）   用户  角色  用户角色中间表
            userIds = sysUserRoleMapper.selectUserIdByRoleName("维修工");
        }
        // 找到超级管理员
        List<Long> managerIds = sysUserRoleMapper.selectUserIdByRoleName("超级管理员");
        // 合并两份用户id
        Collection<Long> allUserIds = CollUtil.addAll(userIds, managerIds);
        // 去重
        allUserIds = CollUtil.distinct(allUserIds);

        // 批量保存异常数据
        List<AlertData> alertDataList = insertAlertData(allUserIds, deviceData, alertRule);

        // websocket推送消息
        websocketNotity(alertDataList.get(0), alertRule,allUserIds);


    }
    /**
     * websocket推送消息
     * @param alertData
     * @param rule
     * @param allUserIds
     */
    private void websocketNotity(AlertData alertData, AlertRule rule, Collection<Long> allUserIds) {

        // 属性拷贝
        AlertNotifyVo alertNotifyVo = BeanUtil.toBean(alertData, AlertNotifyVo.class);
        alertNotifyVo.setFunctionName(rule.getFunctionName());
        alertNotifyVo.setAlertDataType(Integer.valueOf(rule.getAlertDataType()+""));
        alertNotifyVo.setNotifyType(1);
        // 向指定的人推送消息
        webSocketServer.sendMessageToConsumer(alertNotifyVo, allUserIds);

    }

    @Autowired
    private IAlertDataService alertDataService;

    /**
     * 保存异常数据
     * @param allUserIds
     * @param deviceData
     * @param rule
     */
    private List<AlertData> insertAlertData(Collection<Long> allUserIds, DeviceData deviceData, AlertRule rule) {

        // 属性拷贝，从deviceData拷贝到alertData
        AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
        // 关于规则的数据都拷贝不过去
        alertData.setAlertRuleId(rule.getId());
        // 功能名称+运算符+阈值+持续周期+聚合周期
        String reason = CharSequenceUtil.format("{}{}{},持续了{}周期，就报警",rule.getFunctionId(),
                rule.getOperator(),rule.getValue(),rule.getDuration());
        alertData.setAlertReason(reason);
        // 报警状态
        alertData.setStatus(0);
        alertData.setType(Integer.valueOf(rule.getAlertDataType()+""));
        // 批量保存数据了，由于多个人
        List<AlertData> list = allUserIds.stream().map(userId -> {
            // 再次拷贝
            AlertData dBalertData = BeanUtil.toBean(alertData, AlertData.class);
            dBalertData.setUserId(userId);
            dBalertData.setId(null);
            return dBalertData;

        }).collect(Collectors.toList());
        // 批量保存
        alertDataService.saveBatch(list);

        return list;
    }


}
